/*
	This script file is part of the Sonic 3 A.I.R. script software distribution.
	Copyright (C) 2017-2024 by Eukaryot

	Published under the GNU GPLv3 open source software license, see license.txt
	or https://www.gnu.org/licenses/gpl-3.0.en.html
*/



//-------------------------------------------------------//
// Nemesis Decompression                                 //
//-------------------------------------------------------//

function void Nemesis.loadDataToVRAM(u32 sourceInROM, u16 targetInVRAM)
{
	VDP.setupVRAMWrite(targetInVRAM)
	A0 = sourceInROM
	fn0015ba()
}


//# address-hook(0x0015ba) end(0x0015ca)
function void fn0015ba()
{
	pushDxA01345()
	A3 = 0x00167c
	fn0015d6()
}


//# address-hook(0x0015cc) end(0x001602)
function void fn0015cc()
{
	pushDxA01345()
	A3 = 0x001692
	fn0015d6()
}


//# address-hook(0x0015d6)
function void fn0015d6()
{
	A1 = 0xffffaa00
	D2.u16 = u16[A0]
	if (D2.u16 & 0x8000)
		A3 += 0x0a
	A0 += 2

	A5 = D2.u16 * 8
	D2 = 0
	D3 = 8
	D4 = 0
	fn0016a8()

#if STANDALONE
	D5.u16 = u16[A0]
#else
	D5.u16 = (u8[A0] << 8) + u8[A0+1]
#endif
	D6.u16 = 0x10
	A0 += 2
	fn001604()

	popDxA01345()
}


//# address-hook(0x001604) end(0x001644)
//# translated(0x001650) end(0x0016a6)
function void fn001604()
{
	// This D0 assignment is not part of the original code
	//  -> But this leads to the first part inside the loop having no effect
	//  -> And that's needed to reflect the original code's control flow (which is otherwise hard to represent in script)
	D0.s16 = -1

	fn00163c()
}


//# address-hook(0x001646) end(0x00164e)
function void fn001646()
{
	D4 = 0
	D3 = 8
	--D0.s16

	fn00163c()
}


// For game app, this is implemented in C++ for performance reasons
#if !GAMEAPP

function u32 putNybbles(u32 input, u16 count, u8 value)
{
	for (u16 i = 0; i < count; ++i)
	{
		input = (input << 4) | value
	}
	return input
}

#endif


function void fn00163c()
{
	// This function loads graphics data, e.g. for level objects and HUD
	//  -> Uses some kind of run-length encoding

	// Note that when this function is entered, D3 is always 8

	// Variants:
	//  - 0 = Write to VRAM
	//  - 1 = Write to VRAM and XOR output with D2
	//  - 2 = Write to RAM and XOR output with D2
	u8 variant
	if (A3 == 0x00167c)
	{
		variant = 0
	}
	else if (A3 == 0x001686 || (A3 == 0x00169c && A4 == 0xc00004))
	{
		variant = 1
	}
	else if (A3 == 0x00169c)
	{
		variant = 2
	}
	else
	{
		// Invalid / unhandled variant
		assert(false, "Invalid variant for Nemesis data loading")
		return
	}

// 0x00163c:
	if (D0.s16 < 0)
	{
		fn001604_gatherData()
	}

	while (true)
	{
		u16 count = min(D3.u16, D0.u16 + 1)
		D4 = putNybbles(D4, count, D1.u8)

		if (count < D3.u16)
		{
			D3.u16 -= count
			D0.u16 = -1

			fn001604_gatherData()
		}
		else
		{
			// Ready for output
			if (variant == 0)
			{
				VDP.writeData32(D4)
			}
			else if (variant == 1)
			{
				D2 ^= D4
				VDP.writeData32(D2)
			}
			else
			{
				D2 ^= D4
				u32[A4] = D2
				A4 += 4
			}

			D4 = 0
			--A5
			if (A5.u16 == 0)
			{
				D0.u16 -= D3.u16 - 1
				D3.u16 = 0
				return
			}

			D0.u16 -= D3.u16
			D3 = 8
		}
	}
}


function void fn001604_gatherData()
{
// 0x001604:
	D7.u16 = D6.u16 - 8
	D1.u16 = D5.u16 >> D7
	if (D1.u8 >= 0xfc)
	{
		D6.u16 -= 6
		if (D6.u16 < 9)
		{
			D6.u16 += 8
			D5.u16 = (D5.u16 << 8) + u8[A0]
			++A0
		}

		D6.u16 -= 7
		u16 value = D5.u16 >> D6
		D0.u16 = (value & 0x70) >> 4
		D1.u16 = (value & 0x0f)

		if (D6.u16 < 9)
		{
			D6.u16 += 8
			D5.u16 = (D5.u16 << 8) + u8[A0]
			++A0
		}
	}
	else
	{
		u16 offset = u16(D1.u8) * 2
		D6.u16 -= s8[A1 + offset]

		if (D6.u16 < 9)
		{
			D6.u16 += 8
			D5.u16 = (D5.u16 << 8) + u8[A0]
			++A0
		}

		u16 value = u8[A1 + offset + 1]
		D0.u16 = (value & 0xf0) >> 4
		D1.u16 = (value & 0x0f)
	}
}


//# address-hook(0x0016a8) end(0x0016f8)
function void fn0016a8()
{
	D0 = u8[A0]
	++A0
	while (D0.u8 != 0xff)
	{
		D7.u16 = D0.u16
		while (true)
		{
			D0.u8 = u8[A0]
			++A0
			if (D0.u8 >= 0x80)
				break

			// Use atex from D0, pixel data from D7
			D7.u16 = (D7.u16 & 0x0f) | (D0.u8 & 0x70)
			D0.u16 &= 0x0f
			D1.u16 = u16(D0.u8) << 8
			D7.u16 |= D1.u16

			D1 = 9 - D0.u16		// Surprisingly, this is always positive
		#if STANDALONE
			if (D1.s32 < 0)		// But if it is, make sure nothing breaks
			{
				assert(false, stringformat("Encountered invalid Nemesis compressed data at 0x%06x", A0 - 1))
				return
			}
		#endif
			D0.u16 = u16(u8[A0]) << D1
			++A0
			fillMemory_u16(A1 + D0.u16, 1 << D1, D7.u16)
		}
	}
}


//# address-hook(0x001780) end(0x0017d6)
function void LoadRequiredSpritePatterns()
{
	// This function loads sprite patterns when needed (e.g. bosses look broken without this)
	if (u32[0xfffff680] != 0 && u16[0xfffff6f8] == 0)
	{
		A0 = u32[0xfffff680]
		A1 = 0xffffaa00
		A3 = (u16[A0] & 0x8000) ? 0x001686 : 0x00167c
		D2.u16 = u16[A0] & 0x7fff
		A0 += 2

		fn0016a8()

		D5.u16 = (u16(u8[A0]) << 8) + u8[A0+1]
		A0 += 2
		D6 = 0x10

		u32[0xfffff680] = A0
		u32[0xfffff6e0] = A3
		u32[0xfffff6e4] = 0
		u32[0xfffff6e8] = 0
		u32[0xfffff6ec] = 0
		u32[0xfffff6f0] = D5
		u32[0xfffff6f4] = D6
		u16[0xfffff6f8] = D2.u16
	}
}


function void Nemesis.LoadMultipleToVRAM(u32 address)
{
	A1 = address
	Nemesis.LoadMultipleToVRAM()
}

//# address-hook(0x001892) end(0x0018b2)
function void Nemesis.LoadMultipleToVRAM()
{
	// This gets used only in the Blue Spheres mode Results Screen
	u16 count = u16[A1] + 1
	A1 += 2
	for (u16 i = 0; i < count; ++i)
	{
		u32 sourceInROM = u32[A1]
		u16 targetInVRAM = u16[A1+4]
		A1 += 6
		Nemesis.loadDataToVRAM(sourceInROM, targetInVRAM)
	}
}
